﻿namespace JunionRobotTaskManager.Models.ElevatorControl
{
    public class ControlStream
    {
        public Elevator Belonger { get; init; }

        public int SourceFloor { get; private set; } = -100;

        public int TargetFloor { get; private set; } = -100;

        public bool InExecution { get; private set; }

        public Queue<Step> Steps { get; private set; }

        public Step CurrentStep { get; private set; }

        public Step PreviousStep { get; private set; }

        public ControlStream(Elevator belonger)
        {
            Belonger = belonger;
        }

        public void StartDefault(int sourceFloor, int targetFloor)
        {
            if (sourceFloor > 64 || sourceFloor < -64 || targetFloor > 64 || targetFloor < -64 || sourceFloor == 0 || targetFloor == 0)
            {
                throw new ArgumentException("floor more than 64 or less than -64 or equal to 0");
            }
            if (!InExecution)
            {
                Steps = new Queue<Step>();
                Steps.Enqueue(new Step(0, Command.EnterElevator, this));
                Steps.Enqueue(new Step(1, Command.CloseDoor, this));
                Steps.Enqueue(new Step(2, Command.LeaveElevator, this));
                SourceFloor = sourceFloor;
                TargetFloor = targetFloor;
                InExecution = true;
                CurrentStep = Steps.Peek();
            }
        }

        public void Start(Dictionary<int, Command> steps, int sourceFloor, int targetFloor)
        {
            if (sourceFloor > 64 || sourceFloor < -64 || targetFloor > 64 || targetFloor < -64 || sourceFloor == 0 || targetFloor == 0)
            {
                throw new ArgumentException("floor more than 64 or less than -64 or equal to 0");
            }
            if (!InExecution)
            {
                Steps = new Queue<Step>();
                steps = steps.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
                foreach (var step in steps)
                {
                    Steps.Enqueue(new Step(step.Key, step.Value, this));
                }
                SourceFloor = sourceFloor;
                TargetFloor = targetFloor;
                InExecution = true;
                CurrentStep = Steps.Peek();
            }
        }

        public async Task<bool> ExecuteCurrentStepAsync(bool forward)
        {
            if (!(InExecution && Steps?.Count > 0))
            {
                Belonger.Logger?.Invoke($"elevator({Belonger.Id}) stream-stpes count is 0 or stream not in execution)", null);
            }
            else
            {
                Belonger.Logger?.Invoke($"elevator({Belonger.Id}) stream ready execute ({CurrentStep.Number}, {CurrentStep.Command})", null);
                await CurrentStep.ExecuteAsync();
                Belonger.Logger?.Invoke($"elevator({Belonger.Id}) stream current step ({CurrentStep.Number}, {CurrentStep.Command}) execute result:{CurrentStep.IsComplete}", null);
                if (CurrentStep.IsComplete)
                {
                    if (forward)
                    {
                        PreviousStep = CurrentStep;
                        Steps.Dequeue();
                    }
                    CurrentStep = Steps.Count > 0 ? Steps.Peek() : null;
                    return true;
                }
            }
            return false;
        }

        public async Task<bool> ExecutePreviousStepAsync()
        {
            if (!InExecution || PreviousStep == null)
            {
                return false;
            }
            else
            {
                return await PreviousStep.ExecuteAsync();
            }
        }

        public bool SkipForward(int targetNumber)
        {
            if (!(InExecution && Steps?.Count > 0))
            {
                return false;
            }
            else if (CurrentStep.Number >= targetNumber)
            {
                throw new ArgumentException($"target number({targetNumber}) less than current step number({CurrentStep.Number})");
            }
            else
            {
                while (CurrentStep.Number != targetNumber)
                {
                    PreviousStep = CurrentStep;
                    Steps.Dequeue();
                    if (Steps.Count > 0)
                    {
                        CurrentStep = Steps.Peek();
                    }
                    else
                    {
                        CurrentStep = null;
                        if (CurrentStep?.Number != targetNumber)
                        {
                            throw new ArgumentException($"not found taget({targetNumber})");
                        }
                    }
                }
                return true;
            }
        }

        public void Stop()
        {
            if (InExecution)
            {
                InExecution = false;
                Steps = null;
                SourceFloor = -100;
                TargetFloor = -100;
                PreviousStep = null;
                CurrentStep = null;
                Belonger.Reset();
            }
        }

        public void Abort()
        {
            InExecution = false;
            Steps = null;
            SourceFloor = -100;
            TargetFloor = -100;
            PreviousStep = null;
            CurrentStep = null;
            Belonger.Reset();
        }
    }
}
